package models

import (
	"bigdevops/src/common"
	"encoding/json"
	"fmt"
	"gorm.io/gorm"
	"gorm.io/gorm/clause"
	appsv1 "k8s.io/api/apps/v1"
	corev1 "k8s.io/api/core/v1"
	"k8s.io/apimachinery/pkg/api/resource"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)

// 面向运维的k8s项目
type K8sInstance struct {
	Model         // 不用每次写ID 和 createAt了
	Name   string `json:"name" validate:"required,min=1,max=200" gorm:"uniqueIndex:name_k8s_app_id;type:varchar(100);comment:k8s英文集群名称，支持使用通配符*进行模糊搜索" `
	UserID uint

	Cluster string `json:"cluster" gorm:"type:varchar(100);comment:k8s英文集群名称，支持使用通配符*进行模糊搜索" `
	ContainerCore
	Image    string `json:"image"`
	Replicas int    `json:"replicas"`

	// 一对多关系 https://gorm.io/zh_CN/docs/has_many.html
	K8sAppID uint `gorm:"uniqueIndex:name_k8s_app_id;"`

	K8sAppName string `json:"k8sAppName" gorm:"-"` //给前端表格

	CreateUserName string      `json:"createUserName" gorm:"-"`
	NodePath       string      `json:"nodePath" gorm:"-"`  // a.b.c.d
	Key            string      `json:"key" gorm:"-"`       //给前端表格
	Namespace      string      `json:"namespace" gorm:"-"` //给前端表格
	K8sAppObj      *K8sApp     `json:"k8SAppObj" gorm:"-"`
	ClusterObj     *K8sCluster `json:"clusterObj"  gorm:"-"`
	ReadyStatus    string      `json:"readyStatus"`
}

func DeleteK8sInstanceOneByInstanceId(iid string) error {

	return Db.Select(clause.Associations).Unscoped().Where("instance_id = ?", iid).Delete(&K8sInstance{}).Error
}

func (obj *K8sInstance) CreateOne() error {
	return Db.Create(obj).Error

}

func (obj *K8sInstance) UpdateOne() error {
	return Db.Where("id = ?", obj.ID).Updates(obj).Error

}

func (obj *K8sInstance) DeleteOne() error {

	return Db.Select(clause.Associations).Unscoped().Delete(obj).Error

}

// 后转前 ：需要使用关联表结构的时候
func (obj *K8sInstance) FillFrontAllData() error {
	dbUser, _ := GetUserById(int(obj.UserID))
	if dbUser != nil {
		obj.CreateUserName = dbUser.Username
	}
	if obj.Replicas == 0 {
		obj.Replicas = 1
	}

	// 填充cluster对象
	if obj.ClusterObj == nil {

		dbCluster, _ := GetK8sClusterByName(obj.Cluster)
		if dbCluster != nil {
			obj.ClusterObj = dbCluster
		}
	}

	// 填充父级的app对象，并且让他也填充一下自己的对象
	dbApp, _ := GetK8sAppById(int(obj.K8sAppID))
	if dbApp != nil {
		dbApp.FillFrontAllData()

		obj.K8sAppObj = dbApp
		obj.K8sAppName = dbApp.Name
		obj.Namespace = dbApp.Namespace
		obj.NodePath = dbApp.NodePath
	}
	// 把volume 从json string 转化成数组返回给前端
	var vj []OneVolume
	if obj.VolumeJson != "" {
		err := json.Unmarshal([]byte(obj.VolumeJson), &vj)
		if err != nil {
			return err
		}
		obj.VolumeJsonFront = vj
	}

	// 把ports 从json string 转化成数组返回给前端
	var ps []corev1.ServicePort
	if obj.PortJson != "" {
		err := json.Unmarshal([]byte(obj.PortJson), &ps)
		if err != nil {
			return err
		}
		obj.PortJsonFront = ps
	}

	obj.Key = fmt.Sprintf("%d", obj.ID)

	obj.EnvsFront = common.GenFormKvFromStringArray(obj.Envs)
	obj.LabelsFront = common.GenFormKvFromStringArray(obj.Labels)
	obj.CommandsFront = common.GenFormKvFromStringArrayOnlyKey(obj.Commands)
	obj.ArgsFront = common.GenFormKvFromStringArrayOnlyKey(obj.Args)

	return nil
}

func (obj *K8sInstance) FillDefaultData() (err error) {

	obj.Envs = common.GenStringArrayByFormKv(obj.EnvsFront)
	obj.Labels = common.GenStringArrayByFormKv(obj.LabelsFront)
	obj.Commands = common.GenStringArrayByFormKvOnlyKey(obj.CommandsFront)
	obj.Args = common.GenStringArrayByFormKvOnlyKey(obj.ArgsFront)

	return nil
}
func (obj *K8sInstance) GenDeployment() (*appsv1.Deployment, error) {
	if obj.K8sAppObj == nil {
		return nil, fmt.Errorf("K8sAppObj.nil")

	}
	dep := &appsv1.Deployment{}

	dep.Name = fmt.Sprintf("%v-%v", obj.K8sAppObj.Name, obj.Name)
	dep.Namespace = obj.K8sAppObj.Namespace

	if obj.Replicas > 0 {
		var tmp *int32
		var tmp1 int32 = 0
		tmp1 = int32(obj.Replicas)
		tmp = &tmp1

		dep.Spec.Replicas = tmp
	}
	dep.Spec.Selector = new(metav1.LabelSelector)
	// pod 按节点打散 1个节点最多1个pod ：pod
	// 防止1个节点宕机影响多个副本pod
	// 节点反亲和配置 https://kubernetes.io/zh-cn/docs/concepts/scheduling-eviction/assign-pod-node/
	/*
	   podAntiAffinity:
	     requiredDuringSchedulingIgnoredDuringExecution:
	     - weight: 100
	       podAffinityTerm:
	         labelSelector:
	           matchExpressions:
	           - key: common.K8S_APP_LABEL_INS_NAME
	             operator: In
	             values:
	             - instance名称
	         topologyKey: kubernetes.io/hostname
	*/

	//labelSelector, _ := labels.Parse(fmt.Sprintf("%v=%v", common.K8S_APP_LABEL_INS_NAME, obj.Name))
	affinity := &corev1.Affinity{

		PodAntiAffinity: nil,
	}

	podAffinityTerm := corev1.PodAffinityTerm{
		LabelSelector: &metav1.LabelSelector{
			MatchLabels: nil,
			MatchExpressions: []metav1.LabelSelectorRequirement{
				{
					Key:      common.K8S_APP_LABEL_INS_NAME,
					Operator: metav1.LabelSelectorOpIn,
					Values:   []string{obj.Name},
				},
				{
					Key:      common.K8S_APP_LABEL_APP_NAME,
					Operator: metav1.LabelSelectorOpIn,
					Values:   []string{obj.K8sAppName},
				},
			},
		},
		Namespaces:  nil,
		TopologyKey: "kubernetes.io/hostname",
		NamespaceSelector: &metav1.LabelSelector{
			MatchLabels:      nil,
			MatchExpressions: nil,
		},
	}
	podAntiAffinity := &corev1.PodAntiAffinity{
		RequiredDuringSchedulingIgnoredDuringExecution: []corev1.PodAffinityTerm{
			podAffinityTerm,
		},
		//PreferredDuringSchedulingIgnoredDuringExecution: []corev1.WeightedPodAffinityTerm{
		//	{
		//		Weight: 100,
		//		PodAffinityTerm: corev1.PodAffinityTerm{
		//			LabelSelector: &metav1.LabelSelector{
		//				MatchLabels: nil,
		//				MatchExpressions: []metav1.LabelSelectorRequirement{{
		//					Key:      common.K8S_APP_LABEL_INS_NAME,
		//					Operator: metav1.LabelSelectorOpIn,
		//					Values:   []string{obj.Name},
		//				}},
		//			},
		//			Namespaces:  nil,
		//			TopologyKey: "kubernetes.io/hostname",
		//			NamespaceSelector: &metav1.LabelSelector{
		//				MatchLabels:      nil,
		//				MatchExpressions: nil,
		//			},
		//		},
		//	},
		//},
	}
	affinity.PodAntiAffinity = podAntiAffinity
	// TODO 先注释掉 反亲和
	//dep.Spec.Template.Spec.Affinity = affinity

	// 配置镜像拉取secret 如果有需要自己配置
	//dep.Spec.Template.Spec.ImagePullSecrets = []corev1.LocalObjectReference{{Name: ""}}

	// 新建svc svc是app维度的

	// instance 可以继承 app的：containerCore字段 instance 可以不填：不填就得用app的
	// 如果instance 填了，就用自己的

	// 拼接端口：端口来自svc的port 不能变的，来自app

	// 并且配置就绪探针
	readinessProbe := &corev1.Probe{}
	readinessProbeOk := false
	needReadinessProbe := false
	if len(obj.K8sAppObj.PortJsonFront) > 0 {
		needReadinessProbe = true
	}
	cPort := []corev1.ContainerPort{}
	for _, svcPort := range obj.K8sAppObj.PortJsonFront {
		one := corev1.ContainerPort{
			Name:          svcPort.Name,
			ContainerPort: int32(svcPort.TargetPort.IntValue()),
			Protocol:      svcPort.Protocol,
		}
		cPort = append(cPort, one)
		if svcPort.Protocol == "TCP" && !readinessProbeOk {
			readinessProbe.TCPSocket = &corev1.TCPSocketAction{
				Port: svcPort.TargetPort,
			}
			readinessProbe.PeriodSeconds = 10

			readinessProbeOk = true
		}
	}

	c := corev1.Container{
		Name:      obj.Name,
		Image:     obj.Image,
		Ports:     cPort,
		Resources: corev1.ResourceRequirements{},
		//ImagePullPolicy: corev1.PullIfNotPresent,
		ImagePullPolicy: corev1.PullAlways,
	}
	if needReadinessProbe {
		c.ReadinessProbe = readinessProbe
	}
	// ContainerCore 需要判断的点
	// 去重追加  envs
	envM := common.GenMapByKvStrings(obj.K8sAppObj.Envs)
	envThis := common.GenMapByKvStrings(obj.Envs)

	for k, v := range envThis {
		envM[k] = v
	}
	envVars := []corev1.EnvVar{}
	for k, v := range envM {
		envVars = append(envVars, corev1.EnvVar{
			Name:  k,
			Value: v,
		})
	}
	c.Env = envVars

	// 去重追加    labels
	//template:
	//    metadata:
	//      creationTimestamp: null
	//      labels:
	//        app: mysql-57
	labelM := common.GenMapByKvStrings(obj.K8sAppObj.Labels)

	// 为了保持一致性 标签选择器只写这3个，这3个是不受用户控制的
	// spec:
	//  progressDeadlineSeconds: 600
	//  replicas: 1
	//  revisionHistoryLimit: 10
	//  selector:
	//    matchLabels:
	//      infra_app_name: update-app
	//      infra_instance_name: rc01
	//      infra_stree_node: tencent.IEG.Game.CF
	fixLabelM := map[string]string{
		common.K8S_APP_LABEL_STREE_NAME: obj.K8sAppObj.StreeNodeObj.NodePath,
		common.K8S_APP_LABEL_APP_NAME:   obj.K8sAppObj.Name,
		common.K8S_APP_LABEL_INS_NAME:   obj.Name,
	}

	// 绑定服务树表示标签：app下面多个instance一致
	labelM[common.K8S_APP_LABEL_STREE_NAME] = obj.K8sAppObj.StreeNodeObj.NodePath
	// 父级app的name标签 ：app下面多个instance一致
	labelM[common.K8S_APP_LABEL_APP_NAME] = obj.K8sAppObj.Name
	// 这个instance的自己的标签 独特的
	labelM[common.K8S_APP_LABEL_INS_NAME] = obj.Name

	labelThis := common.GenMapByKvStrings(obj.Labels)

	for k, v := range labelThis {
		labelM[k] = v
	}
	// 必须要设置 标签和选择器一致，不然报错
	/*
		spec:
		  replicas: 1
		  selector:
		    matchLabels:
		      l1: v1
		      l2: v1
		  template:
		    metadata:
		      creationTimestamp: null
		      labels:
		        l1: v1
		        l2: v1

	*/
	dep.Spec.Template.Labels = labelM
	dep.Spec.Selector.MatchLabels = fixLabelM
	dep.Labels = labelM
	// svc后面的标签选择器  需要单独设置，不能携带这个instance的独特标签

	//	覆盖策略 command args resource
	if len(obj.Commands) > 0 && obj.Commands[0] != "" {
		c.Command = obj.Commands
	} else if len(obj.K8sAppObj.Commands) > 0 && obj.K8sAppObj.Commands[0] != "" {

		c.Command = obj.K8sAppObj.Commands
	}

	if len(obj.Args) > 0 && obj.Args[0] != "" {
		c.Args = obj.Args
	} else if len(obj.K8sAppObj.Args) > 0 && obj.K8sAppObj.Args[0] != "" {
		c.Args = obj.K8sAppObj.Args
	}

	//if obj.K8sAppObj.Args != "" {
	//	c.Args = []string{obj.K8sAppObj.Args}
	//}
	//
	//if obj.Args != "" {
	//	c.Args = []string{obj.Args}
	//}

	//	覆盖策略 resource
	requestM := corev1.ResourceList{}
	limitM := corev1.ResourceList{}

	if obj.CpuRequest != "" {
		formatResourceOneKey(corev1.ResourceCPU, obj.CpuRequest, requestM)

	} else if obj.K8sAppObj.CpuRequest != "" {
		formatResourceOneKey(corev1.ResourceCPU, obj.K8sAppObj.CpuRequest, requestM)

	} else if obj.ClusterObj.CpuRequest != "" {
		formatResourceOneKey(corev1.ResourceCPU, obj.ClusterObj.CpuRequest, requestM)

	}

	if obj.MemoryRequest != "" {
		formatResourceOneKey(corev1.ResourceMemory, obj.MemoryRequest, requestM)

	} else if obj.K8sAppObj.MemoryRequest != "" {
		formatResourceOneKey(corev1.ResourceMemory, obj.K8sAppObj.MemoryRequest, requestM)

	} else if obj.ClusterObj.MemoryRequest != "" {
		formatResourceOneKey(corev1.ResourceMemory, obj.ClusterObj.MemoryRequest, requestM)

	}

	if obj.CpuLimit != "" {
		formatResourceOneKey(corev1.ResourceCPU, obj.CpuLimit, limitM)

	} else if obj.K8sAppObj.CpuLimit != "" {
		formatResourceOneKey(corev1.ResourceCPU, obj.K8sAppObj.CpuLimit, limitM)

	} else if obj.ClusterObj.CpuLimit != "" {
		formatResourceOneKey(corev1.ResourceCPU, obj.ClusterObj.CpuLimit, limitM)

	}

	if obj.MemoryLimit != "" {
		formatResourceOneKey(corev1.ResourceMemory, obj.CpuLimit, limitM)

	} else if obj.K8sAppObj.MemoryLimit != "" {
		formatResourceOneKey(corev1.ResourceMemory, obj.K8sAppObj.MemoryLimit, limitM)

	} else if obj.ClusterObj.MemoryLimit != "" {
		formatResourceOneKey(corev1.ResourceMemory, obj.ClusterObj.MemoryLimit, limitM)

	}
	c.Resources.Requests = requestM
	c.Resources.Limits = limitM

	// 合并处理volume
	vm := map[string]OneVolume{}

	for _, v := range obj.VolumeJsonFront {
		vm[v.Name] = v
	}
	for _, v := range obj.K8sAppObj.VolumeJsonFront {
		vm[v.Name] = v
	}
	volumeMounts := []corev1.VolumeMount{}
	volumes := []corev1.Volume{}
	for _, v := range vm {
		volumeMounts = append(volumeMounts, corev1.VolumeMount{
			Name:      v.Name,
			MountPath: v.MountPath,
			SubPath:   v.SubPath,
		})

		//volume:=
		switch v.Type {
		case common.K8S_VOLUME_TYPE_CONFIGMAP:
			volumes = append(volumes, corev1.Volume{
				Name: v.Name,
				VolumeSource: corev1.VolumeSource{ConfigMap: &corev1.ConfigMapVolumeSource{
					LocalObjectReference: corev1.LocalObjectReference{
						Name: v.CmName,
					},
				}},
			})
		case common.K8S_VOLUME_TYPE_PVC:
			volumes = append(volumes, corev1.Volume{
				Name: v.Name,
				VolumeSource: corev1.VolumeSource{PersistentVolumeClaim: &corev1.PersistentVolumeClaimVolumeSource{
					ClaimName: v.PvcName,
				}},
			})
		case common.K8S_VOLUME_TYPE_HOSTPATH:

			tmp := corev1.HostPathType(v.HostPathType)
			var t *corev1.HostPathType
			t = &tmp

			volumes = append(volumes, corev1.Volume{
				Name: v.Name,
				VolumeSource: corev1.VolumeSource{HostPath: &corev1.HostPathVolumeSource{
					Path: v.HostPathPath,
					Type: t,
				}},
			})

		}

	}

	c.VolumeMounts = volumeMounts
	dep.Spec.Template.Spec.Volumes = volumes
	dep.Spec.Template.Spec.Containers = []corev1.Container{c}

	return dep, nil
}

func formatResourceOneKey(resourceName corev1.ResourceName, resourceValue string, m corev1.ResourceList) {
	//m[resourceName] = resource.MustParse(resourceValue)
	m[resourceName], _ = resource.ParseQuantity(resourceValue)
}

func GetK8sInstanceCount() int {
	var count int64
	Db.Table("K8sInstancees").Count(&count)
	return int(count)
}

func GetK8sInstanceAllWithLimitOffset(limit, offset int) (objs []*K8sInstance, err error) {
	err = Db.Limit(limit).Offset(offset).Find(&objs).Error
	return
}

func GetK8sInstanceByK8sAppId(k8sAppId int) (objs []*K8sInstance, err error) {
	err = Db.Where("k8s_app_id = ?", k8sAppId).Find(&objs).Error
	return
}

// select * from ecs where
func GetK8sInstanceByIdsWithLimitOffset(ids []int, limit, offset int) (objs []*K8sInstance, err error) {
	err = Db.Limit(limit).Offset(offset).Find(&objs, ids).Error
	return
}

func GetK8sInstanceByK8sAppIdAndName(k8sAppId uint, name string) (*K8sInstance, error) {

	var dbObj K8sInstance
	err := Db.Where("k8s_app_id = ? and name = ?", k8sAppId, name).First(&dbObj).Error
	if err != nil {
		if err == gorm.ErrRecordNotFound {
			return nil, fmt.Errorf("K8sInstance不存在")
		}
		return nil, fmt.Errorf("数据库错误:%w", err)
	}
	return &dbObj, nil

}
func GetK8sInstanceAll() (objs []*K8sInstance, err error) {
	err = Db.Find(&objs).Error
	return
}

func GetK8sInstanceByClusterAndAppId(appId int) (objs []*K8sInstance, err error) {
	err = Db.Where("k8s_app_id= ?", appId).Find(&objs).Error
	return
}

func GetK8sInstanceById(id int) (*K8sInstance, error) {

	var dbObj K8sInstance
	err := Db.Where("id = ?", id).First(&dbObj).Error
	if err != nil {
		if err == gorm.ErrRecordNotFound {
			return nil, fmt.Errorf("K8sInstance不存在")
		}
		return nil, fmt.Errorf("数据库错误:%w", err)
	}
	return &dbObj, nil

}
